import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.svm import SVR
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
import numpy as np
from sklearn.metrics import mean_squared_error
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm

# 1. 数据预处理和清洗
# 读取CSV文件
df = pd.read_csv('./data/数据分析/Coffee_Chain_Sales.csv')
# 将Date列转换为datetime格式
df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
# 提取年份、月份作为新特征
df['Year'] = df['Date'].dt.year
df['Month'] = df['Date'].dt.month

# 2. 特征提取
# 选择用于预测的特征和目标变量
features = df[['Year', 'Month', 'AreaCode', 'Cogs', 'Margin', 'ActualandProfit', 'InventoryMargin']]
target = df['Sales']
# 划分训练集和测试集
# features：所要划分的样本特征集
# target：所要划分的样本结果
# test_size：样本占比，如果是整数的话就是样本的数量
# random_state：是随机数的种子。
# 随机数种子：其实就是该组随机数的编号，在需要重复试验的时候，保证得到一组一样的随机数。比如你每次都填1，其他参数一样的情况下你得到的随机数组是一样的。但填0或不填，每次都会不一样。
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
# 特征缩放 是来自 `sklearn.preprocessing` 模块的一个类，其作用是进行特征缩放，使得所有特征的均值为 0，标准差为 1。这种处理方式也被称为数据的标准化（Standardization）或者 Z-Score 标准化。

scaler = StandardScaler()
#  `fit` 函数用来计算数据的均值和标准差，为之后的缩放做准备。在调用 `fit` 函数后，`StandardScaler()` 对象会保存计算得到的均值和标准差。
# - `X_train` 是输入数据，它应该是一个二维数组（可以是列表的列表、numpy 数组或者其他形式的二维结构）。每一行代表一个样本，每一列代表一个特征。
# 虽然 `fit` 函数会计算均值和标准差，但它并不会直接对输入数据进行缩放。如果你想要在计算均值和标准差的同时进行缩放，
# 可以使用 `fit_transform` 函数，或者在调用 `fit` 函数后，再调用 `transform` 函数。
# 这两种方法都会得到一个新的数组，其中的数据已经被缩放为均值为0、标准差为1。这可以帮助去除数据中的单位差异，让数据更好地适应许多机器学习算法。
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 使用线性回归模型进行预测 指在统计学中是指在统计学中用来描述一个或者多个自变量和一个因变量之间线性关系的回归模型
lr_model = LinearRegression()
# 训练模型
lr_model.fit(X_train_scaled, y_train)
# 预测新数据
lr_predictions = lr_model.predict(X_test_scaled)

# 使用支持向量机回归模型进行预测 SVR全称是SVM（支持向量机support vector machine）对回归问题的一种运用
#SVR模型可以简单理解为，在线性函数的两侧创造了一个“间隔带”，而这个“间隔带”的间距为ϵ（这个值常是根据经验而给定的），
# 对所有落入到间隔带内的样本不计算损失，也就是只有支持向量才会对其函数模型产生影响，最后通过最小化总损失和最大化间隔来得出优化后的模型。
# 对于非线性的模型，与SVM一样使用核函数（kernel function）映射到特征空间，然后再进行回归。
svr_model = SVR(kernel='rbf')
# 训练模型
svr_model.fit(X_train_scaled, y_train)
# 预测新数据
svr_predictions = svr_model.predict(X_test_scaled)


X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, shuffle=False)  # shuffle必须为False以保持时间序列的连续性
# 时间序列预测模型（使用ARIMA模型）
# 选择一个合适的ARIMA模型参数(p, d, q)可能需要一些试验，这里假设为(1, 1, 1)作为示例
model = ARIMA(y_train, order=(1, 1, 1))
model_fit = model.fit()
# 发现y_test值内容是多数组，这段代码会报错(steps=len(y_test))[0]
# yhat = model_fit.forecast(steps=len(y_test))[0]  # 进行预测
yhat = model_fit.forecast(steps=len(y_test))

#  多模型对比差值
# 计算两个模型的均方误差
lr_mse = mean_squared_error(y_test, lr_predictions)
svr_mse = mean_squared_error(y_test, svr_predictions)
arima_mse = mean_squared_error(y_test, yhat)
rmse = np.sqrt(lr_mse)
svr_rmse = np.sqrt(svr_mse)
arima_rmse = np.sqrt(arima_mse)

print(f"Linear Regression MSE: {lr_mse}")
print(f"Linear Regression RMSE: {rmse}")
print(f"Support Vector Regression MSE: {svr_mse}")
print(f"Support Vector Regression RMSE: {svr_rmse}")
print(f"arima Regression MSE: {arima_mse}")
print(f"arima Regression RMSE: {arima_rmse}")

# 绘制线性回归模型的预测结果散点图
plt.figure(figsize=(10, 5))
plt.scatter(y_test, lr_predictions, label='Linear Regression Predictions')
plt.xlabel('Actual Sales')
plt.ylabel('Predicted Sales')
plt.title('Cost profit Linear Regression Predictions vs Actual Sales')
# plt.legend()
# plt.savefig('linear_regression_predictions.png')  # 保存图像文件到当前目录
# plt.close()  # 关闭图像，释放资源
plt.legend()
plt.show()

# 绘制支持向量机回归模型的预测结果散点图
plt.figure(figsize=(10, 5))
plt.scatter(y_test, svr_predictions, label='SVR Predictions')
plt.xlabel('Actual Sales')
plt.ylabel('Predicted Sales')
plt.title('Cost profit SVR Predictions vs Actual Sales')
plt.legend()
plt.show()

# 绘制均方误差对比图（使用条形图）
models = ['Linear Regression', 'SVR']
mses = [lr_mse, svr_mse]
plt.figure(figsize=(10, 5))
plt.bar(models, mses, color='blue')
plt.xlabel('Model')
plt.ylabel('Mean Squared Error')
plt.title('Cost profit MSE Comparison between Models')
plt.show()

# 可视化预测结果与实际值的对比（仅展示线性回归模型的结果）散点图
plt.scatter(y_test, lr_predictions)
plt.xlabel('Actual Sales')
plt.ylabel('Predicted Sales')
plt.title('Cost profit Actual vs Predicted Sales (Linear Regression)')
plt.show()

# 缩小范围进行预测
features = df[['Year', 'Month', 'Sales']]  # 简化为时间序列预测示例
target = df['Sales']

# 划分训练集和测试集
# features：所要划分的样本特征集
# target：所要划分的样本结果
# test_size：样本占比，如果是整数的话就是样本的数量
# random_state：是随机数的种子。
# 随机数种子：其实就是该组随机数的编号，在需要重复试验的时候，保证得到一组一样的随机数。比如你每次都填1，其他参数一样的情况下你得到的随机数组是一样的。但填0或不填，每次都会不一样。
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, random_state=42)
# 特征缩放 是来自 `sklearn.preprocessing` 模块的一个类，其作用是进行特征缩放，使得所有特征的均值为 0，标准差为 1。这种处理方式也被称为数据的标准化（Standardization）或者 Z-Score 标准化。

scaler = StandardScaler()
#  `fit` 函数用来计算数据的均值和标准差，为之后的缩放做准备。在调用 `fit` 函数后，`StandardScaler()` 对象会保存计算得到的均值和标准差。
# - `X_train` 是输入数据，它应该是一个二维数组（可以是列表的列表、numpy 数组或者其他形式的二维结构）。每一行代表一个样本，每一列代表一个特征。
# 虽然 `fit` 函数会计算均值和标准差，但它并不会直接对输入数据进行缩放。如果你想要在计算均值和标准差的同时进行缩放，
# 可以使用 `fit_transform` 函数，或者在调用 `fit` 函数后，再调用 `transform` 函数。
# 这两种方法都会得到一个新的数组，其中的数据已经被缩放为均值为0、标准差为1。这可以帮助去除数据中的单位差异，让数据更好地适应许多机器学习算法。
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 使用线性回归模型进行预测 指在统计学中是指在统计学中用来描述一个或者多个自变量和一个因变量之间线性关系的回归模型
lr_model = LinearRegression()
# 训练模型
lr_model.fit(X_train_scaled, y_train)
# 预测新数据
lr_predictions = lr_model.predict(X_test_scaled)

# 使用支持向量机回归模型进行预测 SVR全称是SVM（支持向量机support vector machine）对回归问题的一种运用
#SVR模型可以简单理解为，在线性函数的两侧创造了一个“间隔带”，而这个“间隔带”的间距为ϵ（这个值常是根据经验而给定的），
# 对所有落入到间隔带内的样本不计算损失，也就是只有支持向量才会对其函数模型产生影响，最后通过最小化总损失和最大化间隔来得出优化后的模型。
# 对于非线性的模型，与SVM一样使用核函数（kernel function）映射到特征空间，然后再进行回归。
svr_model = SVR(kernel='rbf')
# 训练模型
svr_model.fit(X_train_scaled, y_train)
# 预测新数据
svr_predictions = svr_model.predict(X_test_scaled)

X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.2, shuffle=False)  # shuffle必须为False以保持时间序列的连续性
# 时间序列预测模型（使用ARIMA模型）
# 选择一个合适的ARIMA模型参数(p, d, q)可能需要一些试验，这里假设为(1, 1, 1)作为示例
model = ARIMA(y_train, order=(1, 1, 1))
model_fit = model.fit()
# 发现y_test值内容是多数组，这段代码会报错(steps=len(y_test))[0]
# yhat = model_fit.forecast(steps=len(y_test))[0]  # 进行预测
yhat = model_fit.forecast(steps=len(y_test))

#  多模型对比差值
# 计算两个模型的均方误差
lr_mse = mean_squared_error(y_test, lr_predictions)
svr_mse = mean_squared_error(y_test, svr_predictions)
arima_mse = mean_squared_error(y_test, yhat)
rmse = np.sqrt(lr_mse)
svr_rmse = np.sqrt(svr_mse)
arima_rmse = np.sqrt(arima_mse)

print(f"Linear Regression MSE: {lr_mse}")
print(f"Linear Regression RMSE: {rmse}")
print(f"Support Vector Regression MSE: {svr_mse}")
print(f"Support Vector Regression RMSE: {svr_rmse}")
print(f"arima Regression MSE: {arima_mse}")
print(f"arima Regression RMSE: {arima_rmse}")


# 4. 多元回归分析（这里以'Profit'利润作为因变量进行示例分析）
X = df[['Year', 'Month', 'Sales', 'Cogs', 'Margin', 'ActualandProfit', 'InventoryMargin']]
y = df['Profit']  # 假设CSV中有'Profit'利润这一列作为因变量
# train_test_split方法能够将数据集按照用户的需要指定划分为训练集和测试集/
X_train_reg, X_test_reg, y_train_reg, y_test_reg = train_test_split(X, y, test_size=0.2, random_state=42)


# 数据标准化（此步骤对于可视化不是必需的，但保留以展示完整流程）
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# 将训练集和测试集标签添加到原始DataFrame中，以便于可视化时区分
df['Dataset'] = 'Not Selected'
df.loc[X_train.index, 'Dataset'] = 'Train'
df.loc[X_test.index, 'Dataset'] = 'Test'

# 使用seaborn绘制散点图以展示Cogs和Profit的分布
plt.figure(figsize=(10, 6))
sns.scatterplot(x='Cogs', y='Profit', hue='Dataset', data=df, alpha=0.7)
plt.title('Scatter Plot of Cogs and Profit for Train and Test Sets')
plt.xlabel('Cogs')
plt.ylabel('Profit')
plt.show()

# 绘制Sales的分布图（直方图）
plt.figure(figsize=(10, 6))
sns.histplot(df['Sales'][df['Dataset'] == 'Train'], label='Train', alpha=0.7, bins=30, kde=True)
sns.histplot(df['Sales'][df['Dataset'] == 'Test'], label='Test', alpha=0.7, bins=30, kde=True)
plt.title('Distribution of Sales for Train and Test Sets')
plt.xlabel('Sales')
plt.ylabel('Frequency')
plt.legend()
plt.show()













# 线性回归普通最小二乘法是寻找参数β1、β2……的估计值，使上式的离差平方和Q达极小值。式中每个平方项的权数相同，是普通最小二乘回归参数估计方法。通俗来讲OLS是寻找让点到斜线形成的方块面积最小的斜率。
# 通过OLS步骤得到系数估计值后会有一定概率有偏差，这与数据的取量、函数设定形式有关系。而系数的标准误便是系数估计值的标准差，用来衡量数值准确程度。
model_reg = sm.OLS(y_train_reg, X_train_reg).fit()
predictions_reg = model_reg.predict(X_test_reg)
# 输出回归分析结果，查看哪些变量对利润有显著影响
print(f"model_reg: {model_reg.summary()}")

# SVR模型和线性回归模型两个模型的MSE的值来看，线性回归模型在预测销售额时表现较好，可能适合用于捕捉销售额与所选特征之间的线性关系。
# 支持向量回归模型（SVR）在预测销售额时表现较差，可能需要进一步调整模型参数或尝试其他类型的回归模型。
# 例如：根据具体的数据集内容进行调整（检查数据中是否存在异常值、缺失值或重复值，并进行相应的处理。
# 对于异常值，可以考虑使用中位数、均值或特定的插值方法进行填充或替换。根据数据分析结果，选择与销售额相关性较高的特征进行建模。
# 可以考虑使用相关性分析、特征重要性评估等方法来辅助特征选择。对于线性回归模型，可以尝试调整正则化参数来避免过拟合；
# 对于SVR模型，可以调整核函数（如尝试使用其他类型的核函数）和相关参数（如C值和gamma值）来优化模型性能。）和业务需求来选择合适的模型如（
#1、决策树，（ID3、C4.5、CART、CHAID、C5.0）适用于处理非线性关系和数据集含有分类变量的情况。决策树易于理解和解释，能够直观地展示决策过程。
#2、随机森林，（RandomForestClassifier）通过集成多个决策树来提高预测精度和稳定性。随机森林能够处理大量的输入变量，并且对于数据集的异常值和噪声具有较好的鲁棒性。
#3、梯度提升树，通过迭代地添加新的决策树来修正之前模型的错误，从而不断提升模型性能。梯度提升树在处理复杂非线性关系时表现优异。），并通过交叉验证等技术来评估模型的性能。

# 绘制线性回归模型的预测结果散点图
plt.figure(figsize=(10, 5))
plt.scatter(y_test, lr_predictions, label='Linear Regression Predictions')
plt.xlabel('Actual Sales')
plt.ylabel('Predicted Sales')
plt.title('Linear Regression Predictions vs Actual Sales')
plt.legend()
# plt.savefig('linear_regression_predictions.png')  # 保存图像文件到当前目录
# plt.legend()
plt.show()
plt.close()  # 关闭图像，释放资源

# 绘制支持向量机回归模型的预测结果散点图
plt.figure(figsize=(10, 5))
plt.scatter(y_test, svr_predictions, label='SVR Predictions')
plt.xlabel('Actual Sales')
plt.ylabel('Predicted Sales')
plt.title('SVR Predictions vs Actual Sales')
plt.legend()
plt.show()

# 绘制均方误差对比图（使用条形图）
models = ['Linear Regression', 'SVR']
mses = [lr_mse, svr_mse]
plt.figure(figsize=(10, 5))
plt.bar(models, mses, color='blue')
plt.xlabel('Model')
plt.ylabel('Mean Squared Error')
plt.title('MSE Comparison between Models')
plt.show()

# 可视化预测结果与实际值的对比（仅展示线性回归模型的结果）散点图
plt.scatter(y_test, lr_predictions)
plt.xlabel('Actual Sales')
plt.ylabel('Predicted Sales')
plt.title('Actual vs Predicted Sales (Linear Regression)')
plt.show()

# 5. 可视化展示
# 展示时间序列预测结果（二维线图）
plt.figure(figsize=(10, 5))
plt.plot(y_test.index, y_test.values, label='Actual Sales')
plt.plot(y_test.index, yhat, label='Predicted Sales')
plt.legend()
plt.title('Actual vs Predicted Sales (ARIMA Model)')
plt.xlabel('Time Period')
plt.ylabel('Sales')
plt.show()

# 展示多元回归分析中显著变量的系数（这里以热图的形式展示）
coefficients = model_reg.params
coefficients_df = pd.DataFrame(coefficients, columns=['Coefficient'])
coefficients_df['Variable'] = coefficients_df.index
plt.figure(figsize=(10, 8))
sns.set(font_scale=1.2)
sns.heatmap(coefficients_df[['Coefficient']].T, annot=True, fmt='.2f')
plt.title('Regression Coefficients Heatmap')
plt.xlabel('Variables')
plt.ylabel('Coefficient')
plt.show()






# 线性回归的应用场景
# 金融领域：例如预测股票价格、货币汇率等。
# 经济学：例如预测国内生产总值、通货膨胀率等。
# 市场营销：例如预测销售额、市场份额等。
# 工程学：例如预测机器零件的寿命、电力消耗等。
# 医学研究：例如预测病人的生存率、药物的剂量等。
# 教育研究：例如预测学生的成绩、教育投入对教育成果的影响等。
# 社会科学：例如预测人口增长率、失业率等。
# 环境科学：例如预测气温、降雨量等
# 计算机科学：例如预测程序的运行时间、网络延迟等。
# 物流管理：例如预测货物的运输时间、成本等。
# 生产制造：例如预测生产线的产量、质量等。
# 农业领域：例如预测作物的产量、生长速度等。

# Python的线性回归模型的实现类库
# NumPy：NumPy是Python科学计算的基础包，其中包含了线性代数、随机数生成等功能，可以用于实现线性回归。
# scikit-learn：scikit-learn是Python中常用的机器学习库，其中包含了多个回归模型，包括线性回归、岭回归、Lasso回归等。
# statsmodels：statsmodels是Python中的统计分析库，其中包含了多个回归模型，包括线性回归、广义线性回归等。
# TensorFlow：TensorFlow是Google开发的深度学习框架，其中也包含了线性回归模型。
# PyTorch：PyTorch是另一个常用的深度学习框架，其中也包含了线性回归模型。
# Keras：Keras是一个高级神经网络API，可以在多个深度学习框架上运行，其中也包含了线性回归模型。
# Theano：Theano是另一个深度学习框架，其中也包含了线性回归模型。
# pandas：pandas是Python中常用的数据处理库，其中也包含了线性回归模型。

# 线性回归模型的评价指标
# 均方误差（Mean Squared Error，MSE）是预测值与真实值之间差值的平方的平均值，用来衡量模型的预测精度，MSE 越小，说明模型的预测结果越准确。
# 均方根误差（Root Mean Squared Error，RMSE）是均方误差的平方根，用来衡量模型的预测精度，RMSE 越小，说明模型的预测结果越准确。
# 平均绝对误差（Mean Absolute Error，MAE）是预测值与真实值之间差值的绝对值的平均值，用来衡量模型的预测精度，MAE 越小，说明模型的预测结果越准确。
# 决定系数（Coefficient of Determination，R-squared）用来衡量模型对数据的拟合程度，取值范围为 0~1，R-squared 越接近 1，说明模型对数据的拟合程度越好。